/*
 * ============================================================================
 *
 *       Filename:  gps.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2014年07月09日 17时29分44秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  jianxi sun (jianxi), ycsunjane@gmail.com
 *   Organization:  
 *
 * ============================================================================
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>  
#include <unistd.h>  
#include <errno.h>
#include <string.h>
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <termios.h>  
#include <sys/file.h>
#include <arpa/inet.h>

#include "main.h"
#include "arg.h"

static int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)  
{ 
	struct termios newtio,oldtio;  
	if  ( tcgetattr( fd,&oldtio)  !=  0) {   
		perror("SetupSerial 1");  
		return -1;  
	}  
	bzero( &newtio, sizeof( newtio ) );  
        /*  canon */
        newtio.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
                |INLCR|IGNCR|ICRNL|IXON);
        newtio.c_oflag &= ~OPOST;
        newtio.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
        newtio.c_cflag &= ~(CSIZE|PARENB);
        newtio.c_cflag |= CS8;

	newtio.c_cflag  |=  CLOCAL | CREAD;  
	newtio.c_cflag &= ~CSIZE;  

	switch( nBits )  
	{  
	case 7:  
		newtio.c_cflag |= CS7;  
		break;  
	case 8:  
		newtio.c_cflag |= CS8;  
		break;  
	}  

	switch( nEvent )  
	{  
	case 'O':  
		newtio.c_cflag |= PARENB;  
		newtio.c_cflag |= PARODD;  
		newtio.c_iflag |= (INPCK | ISTRIP);  
		break;  
	case 'E':   
		newtio.c_iflag |= (INPCK | ISTRIP);  
		newtio.c_cflag |= PARENB;  
		newtio.c_cflag &= ~PARODD;  
		break;  
	case 'N':    
		newtio.c_cflag &= ~PARENB;  
		break;  
	}  

	switch( nSpeed )  
	{  
	case 2400:  
		cfsetispeed(&newtio, B2400);  
		cfsetospeed(&newtio, B2400);  
		break;  
	case 4800:  
		cfsetispeed(&newtio, B4800);  
		cfsetospeed(&newtio, B4800);  
		break;  
	case 9600:  
		cfsetispeed(&newtio, B9600);  
		cfsetospeed(&newtio, B9600);  
		break;  
	case 115200:  
		cfsetispeed(&newtio, B115200);  
		cfsetospeed(&newtio, B115200);  
		break;  
	case 460800:  
		cfsetispeed(&newtio, B460800);  
		cfsetospeed(&newtio, B460800);  
		break;  
	default:  
		cfsetispeed(&newtio, B9600);  
		cfsetospeed(&newtio, B9600);  
		break;  
	}  
	if( nStop == 1 )  
		newtio.c_cflag &=  ~CSTOPB;  
	else if ( nStop == 2 )  
		newtio.c_cflag |=  CSTOPB;  
	newtio.c_cc[VTIME] = 10;
	newtio.c_cc[VMIN] = 255;
	tcflush(fd,TCIFLUSH);  
	if((tcsetattr(fd,TCSANOW,&newtio))!=0)  
	{  
		perror("com set error");  
		return -1;  
	}  
	return 0;  
}

static int one_instance()
{
	char filename[256];
	snprintf(filename, 256, "/var/run/ttlto485_%s.pid",
		args_info.ctldev_arg);

	int pid_file = open(filename, O_CREAT | O_RDWR, 0666);
	int rc = flock(pid_file, LOCK_EX | LOCK_NB);
	if(rc) {
		if(EWOULDBLOCK == errno) {
			pr_debug("ttlto485 another is running\n");
			return -1;
		}
	}
	return 0;
}

void send_crc(uint8_t *p, uint8_t flen)
{
	uint8_t crc = 0;
	int i;
	for(i = 0; i < flen - 1; i++) {
		crc += p[i];
	}

	p[flen - 1] = crc;
}

uint8_t return_crc(uint8_t *q, uint8_t len)
{
	uint8_t crc = 0;
	int i;
	for(i = 0; i < len - 1; i++) {
		crc += q[i];
	}
	return crc;
}

int check_model(struct command *res, uint8_t rlen)
{
	if(memcmp(res->c_head, CHEAD, 2) != 0) {
		pr_debug("c_head failed\n");
		exit(1);
	}

	uint8_t * crc = (uint8_t *)res + rlen - 1;
	if(*crc != return_crc((uint8_t *)res, rlen)){
		pr_debug("read crc failed\n");
		exit(1);
	}
	*crc = 0;

	int i;
	switch(res->cmd) {
	case CMD_FACTORY:
		printf("{\"factory\":\"%0x2\"}\n", res->status);
		break;
	case CMD_WRITE_COUNT:
		printf("{\"count\":\"%d\"}\n", ntohl(res->count));
		break;
	case CMD_CANCEL:
		printf("{\"status\":\"%d\"}\n", !res->status);
		break;
	case CMD_READ_COUNT:
		printf("{\"count\":\"%d\"}\n", ntohl(res->count));
		break;
	default:
		pr_debug("result cmd err\n");
		exit(1);
	}
	return 0;
}

int main(int argc, char *argv[])
{
	proc_args(argc, argv);
	if(one_instance() < 0)
		return -1;
	int fd, ret;

	int rv;
	fd_set set;
	struct timeval timeout;

	fd = open(args_info.ctldev_arg, O_RDWR);
	if(fd < 0) {
		pr_debug("open %s failed:%s\n",
			args_info.ctldev_arg, strerror(errno));
		exit(1);
	}

	FILE *file = fdopen(fd, "w+");
	if(file == NULL) {
		pr_debug("get fd FILE ptr failed:%s\n", strerror(errno));
		exit(1);
	}

	ret = set_opt(fd, SPEED, BITS, EVENT, STOP);
	if(ret < 0) {
		pr_debug("set tty failed:%s\n", strerror(errno));
		exit(1);
	}

	int i;
	struct command *cmd;
	cmd = malloc(sizeof(struct command));
	memset((void *)cmd, 0xff, sizeof(struct command));
	uint8_t *pkt = (void *)cmd;

	cmd->c_head[0] = CHEAD[0];
	cmd->c_head[1] = CHEAD[1];

	switch(args_info.cmd_arg) {
	case CMD_FACTORY:
		cmd->cmd = CMD_FACTORY;
		break;
	case CMD_WRITE_COUNT:
		cmd->cmd = CMD_WRITE_COUNT;
		cmd->count = htonl(args_info.count_arg);
		break;
	case CMD_CANCEL:
		cmd->cmd = CMD_CANCEL;
		break;
	case CMD_READ_COUNT:
		cmd->cmd = CMD_READ_COUNT;
		break;
	default:
		pr_debug("unknow cmd:%x\n", args_info.cmd_arg);
		exit(1);
	}

	send_crc((uint8_t *)cmd, sizeof(struct command));

	for(i = 0; i < sizeof(struct command); i++) {
		pr_debug("%02x ", pkt[i]);
	}
	pr_debug("\n");

	ret = write(fd, (void *)cmd, sizeof(struct command));
	if(ret < 0) {
		pr_debug("write command failed: %s(%d)\n",
			strerror(errno), errno);
		exit(1);
	}

	struct command res;
	FD_ZERO(&set);
	FD_SET(fd, &set);
	timeout.tv_sec = 5;
	timeout.tv_usec = 0;
	rv = select(fd + 1, &set, NULL, NULL, &timeout);
	if(rv == -1) {
		pr_debug("select failed: %s(%d)\n",
			strerror(errno), errno);
		exit(1);
	} else if(rv == 0) {
		pr_debug("read timeout\n");
		exit(1);
	} else {
		ret = read(fd, (void *)&res, sizeof(res));
		if(ret != sizeof(res)) {
			pr_debug("read failed: %d\n", ret);
			exit(1);
		}

		pr_debug("recv: %d\n", ret);
		pkt = (void *)&res;
		for(i = 0; i < ret; i++) {
			pr_debug("%02x ", pkt[i]);
		}
		pr_debug("\n");

		if(check_model(&res, ret) != 0) {
			pr_debug("result error\n");
			exit(1);
		}
	}
	return 0;
}
